using System;
using System.IO;

namespace PWEmu.ZLib
{
    /// <summary>
    /// ZLib output stream. 
    /// Inherites System.IO.Stream.
    /// </summary>
    public class ZOutputStream : Stream
    {
        #region Stream overrides without usage

        public override int Read(byte[] buffer, int offset, int count) { return 0; }

        public override void SetLength(long value) { }

        public override long Seek(long offset, SeekOrigin origin) { return 0; }

        public override bool CanRead { get { return false; } }

        public override bool CanSeek { get { return false; } }

        public override bool CanWrite { get { return false; } }
        /// <summary>Gets the total number of bytes input so far.</summary>
        public override long Length { get { return _zstream.totalIn; } }
        /// <summary>Gets the total number of bytes output so far.</summary>
        public override long Position { get { return _zstream.totalOut; } set { } }

        #endregion

        /// <summary>Gets the total number of bytes input so far.</summary>
        virtual public long TotalIn { get { return _zstream.totalIn; } }

        /// <summary>Gets the total number of bytes output so far.</summary>
        virtual public long TotalOut { get { return _zstream.totalOut; } }

        /// <summary></summary>
        protected internal ZStream _zstream = new ZStream();

        /// <summary></summary>
        protected internal int _bufSize = 4096;

        /// <summary></summary>
        protected internal int _flushRenamedField;

        /// <summary></summary>
        protected internal byte[] _buf;

        /// <summary></summary>
        protected internal byte[] _buf1 = new byte[1];

        /// <summary></summary>
        protected internal bool _compress;

        /// <summary></summary>
        private Stream _outputStream;

        /// <summary>
        /// Starts unpacking for a stream specified.
        /// </summary>
        /// <param name="outputStream">The stream to inflate</param>
        public ZOutputStream(Stream outputStream)
            : base()
        {
            InitBlock();
            _outputStream = outputStream;
            _zstream.InflateInit();
            _compress = false;
        }

        /// <summary>
        /// Starts packing for a stream specified.
        /// </summary>
        /// <param name="outRenamed">The stream to deflate</param>
        /// <param name="level">Pack level</param>
        public ZOutputStream(Stream outRenamed, int level)
            : base()
        {
            InitBlock();
            _outputStream = outRenamed;
            _zstream.DeflateInit(level);
            _compress = true;
        }

        /// <summary>
        /// 
        /// </summary>
        private void InitBlock()
        {
            _flushRenamedField = ZLibConst.StreamStatus.Z_NO_FLUSH;
            _buf = new byte[_bufSize];
        }

        /// <summary>
        /// 
        /// </summary>
        virtual public int FlushMode
        {
            get { return (_flushRenamedField); }
            set { _flushRenamedField = value; }
        }

        /// <summary>
        /// Writes a byte to the stream.
        /// </summary>
        /// <param name="b"></param>
        public override void WriteByte(byte b)
        {
            WriteByte((int)b);
        }

        /// <summary>
        /// Writes a byte in integer representation to the stream.
        /// </summary>
        /// <param name="b"></param>
        public void WriteByte(int b)
        {
            _buf1[0] = (byte)b;
            Write(_buf1, 0, 1);
        }

        /// <summary>
        /// Writes a byte array to the stream.
        /// </summary>
        /// <param name="b1">Array of bytes to write</param>
        /// <param name="offset">Starting offset</param>
        /// <param name="length"></param>
        public override void Write(byte[] b1, int offset, int length)
        {
            if (length == 0)
                return;
            int err;
            byte[] b = new byte[b1.Length];
            Array.Copy(b1, 0, b, 0, b1.Length);
            _zstream.nextIn = b;
            _zstream.nextInIndex = offset;
            _zstream.availableIn = length;
            do
            {
                _zstream.nextOut = _buf;
                _zstream.nextOutIndex = 0;
                _zstream.availableOut = _bufSize;
                if (_compress)
                    err = _zstream.Deflate(_flushRenamedField);
                else
                    err = _zstream.Inflate(_flushRenamedField);
                if (err != ZLibConst.Status.Z_OK && err != ZLibConst.Status.Z_STREAM_END)
                    throw new ZStreamException((_compress ? "De" : "In") + "flating: " + _zstream.message);
                _outputStream.Write(_buf, 0, _bufSize - _zstream.availableOut);
            }
            while (_zstream.availableIn > 0 || _zstream.availableOut == 0);
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual void Finish()
        {
            int err;
            do
            {
                _zstream.nextOut = _buf;
                _zstream.nextOutIndex = 0;
                _zstream.availableOut = _bufSize;
                if (_compress)
                {
                    err = _zstream.Deflate(ZLibConst.StreamStatus.Z_FINISH);
                }
                else
                {
                    err = _zstream.Inflate(ZLibConst.StreamStatus.Z_FINISH);
                }
                if (err != ZLibConst.Status.Z_STREAM_END && err != ZLibConst.Status.Z_OK)
                    throw new ZStreamException((_compress ? "De" : "In") + "flating: " + _zstream.message);
                if (_bufSize - _zstream.availableOut > 0)
                {
                    _outputStream.Write(_buf, 0, _bufSize - _zstream.availableOut);
                }
            }
            while (_zstream.availableIn > 0 || _zstream.availableOut == 0);
            try
            {
                Flush();
            }
            catch
            {
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual void End()
        {
            if (_compress)
            {
                _zstream.DeflateEnd();
            }
            else
            {
                _zstream.InflateEnd();
            }
            _zstream.Free();
            _zstream = null;
        }

        /// <summary>
        /// 
        /// </summary>
        public override void Close()
        {
            try
            {
                Finish();
            }
            catch {}
            finally
            {
                End();
                _outputStream.Close();
                _outputStream = null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public override void Flush()
        {
            _outputStream.Flush();
        }
    }
}